package co.baiku.boot.pay;

import co.baiku.boot.common.exception.AjavaerException;
import co.baiku.boot.common.exception.ApiException;
import co.baiku.boot.common.message.Message;
import co.baiku.boot.common.tools.JsonTools;
import co.baiku.boot.common.tools.ServletTools;
import co.baiku.boot.common.tools.StringTools;
import co.baiku.boot.common.tools.ThreadPoolTools;
import co.baiku.boot.config.AlipayConfig;
import com.alipay.api.*;
import com.alipay.api.domain.*;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.internal.util.AntCertificationUtil;
import com.alipay.api.internal.util.file.FileUtils;
import com.alipay.api.request.*;
import com.alipay.api.response.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;

import static co.baiku.boot.common.tools.StringTools.isBlank;

@Component
@ConditionalOnProperty("ajavaer.alipay.appid")
@EnableConfigurationProperties({AlipayConfig.class})
public class Alipay implements InitializingBean {
    private final Logger LOG = LoggerFactory.getLogger(Alipay.class);
    @Resource
    private AlipayConfig alipayConfig;
    private AlipayClient alipayClient;
    private SignChecker signChecker;

    private final ScheduledExecutorService executor = ThreadPoolTools.newScheduledThreadPool(5, "alipay-schedule-pool-", true);

    @Value("${ajavaer.debug:false}")
    private Boolean isDebug = false;

    public Alipay() {
    }

    public Alipay(AlipayConfig alipayConfig) throws Exception {
        this.alipayConfig = alipayConfig;
        afterPropertiesSet();
    }

    public AlipayConfig getAlipayConfig() {
        return this.alipayConfig;
    }

    /**
     * 获取auth token 或刷新token
     *
     * @param request
     * @return
     */
    public Message<AlipayOpenAuthTokenAppResponse> authTokenApp(AlipayOpenAuthTokenAppRequest request) {
        try {
            AlipayOpenAuthTokenAppResponse execute = alipayConfig.isUseCert() ? alipayClient.certificateExecute(request) : alipayClient.execute(request);
            return response(execute);
        } catch (AlipayApiException e) {
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 统一收单交易支付接口 条形码支付 声波支付
     *
     * 当预授权转支付调用此接口时，若返回  ACQ.AUTH_ORDER_HAS_FINISHED 标识此前订单已经扣过款，不能重新扣款，应该调用 订单信息查询接口，获取支付宝交易流水号
     *
     * @param request <br>
     * @see <a href='https://docs.open.alipay.com/api_1/alipay.trade.pay'>文档</a>
     */
    public Message<AlipayTradePayResponse> tradePay(AlipayTradePayRequest request) {
        request.setNotifyUrl(alipayConfig.getNotifyUrl());
        try {
            AlipayTradePayResponse execute = alipayConfig.isUseCert() ? alipayClient.certificateExecute(request) : alipayClient.execute(request);
            return response(execute);
        } catch (AlipayApiException e) {
            LOG.error("tradePay exception:", e);
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 统一收单交易创建接口
     *
     * @param model
     * @return
     * @see <a href="https://opendocs.alipay.com/apis/api_1/alipay.trade.create">文档</a></a>
     */
    public Message<AlipayTradeCreateResponse> tradeCreate(AlipayTradeCreateModel model) {
        AlipayTradeCreateRequest request = new AlipayTradeCreateRequest();
        request.setBizModel(model);
        request.setNotifyUrl(alipayConfig.getNotifyUrl());
        return tradeCreate(request);
    }

    /**
     * 统一收单交易创建接口
     *
     * @param request
     * @return
     * @see <a href="https://opendocs.alipay.com/apis/api_1/alipay.trade.create">文档</a></a>
     */
    public Message<AlipayTradeCreateResponse> tradeCreate(AlipayTradeCreateRequest request) {
        try {
            if (isDebug) {
                LOG.debug("[Pay Alipay] tradeCreate:" + JsonTools.beanToJson(request));
            }
            AlipayTradeCreateResponse execute = alipayConfig.isUseCert() ? alipayClient.certificateExecute(request) : alipayClient.execute(request);
            LOG.info("[AlipayTradeCreateResponse] " + JsonTools.beanToJson(execute));
            return response(execute);
        } catch (AlipayApiException e) {
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 外部商户APP唤起快捷SDK创建订单并支付
     * <a href='https://docs.open.alipay.com/api_1/alipay.trade.app.pay/'>文档</a>
     *
     * @param model <br>
     *              - subject [Y]	商品的标题/交易标题/订单标题/订单关键字等。<br>
     *              - out_trade_no [Y] 商户网站唯一订单号<br>
     *              - total_amount [Y] 订单总金额，单位为元，精确到小数点后两位，取值范围[0.01,100000000]<br>
     *              - timeout_express 该笔订单允许的最晚付款时间，逾期将关闭交易。取值范围：1m～15d。m-分钟，h-小时，d-天，1c-当天（1c-当天的情况下，无论交易何时创建，都在0点关闭）。 该参数数值不接受小数点， 如 1.5h，可转换为 90m。<br>
     *              - product_code 销售产品码，商家和支付宝签约的产品码<br>
     *              - body 对一笔交易的具体描述信息。如果是多种商品，请将商品描述字符串累加传给body。<br>
     *              - time_expire 绝对超时时间，格式为yyyy-MM-dd HH:mm。<br>
     *              - goods_type 商品主类型 :0-虚拟类商品,1-实物类商品<br>
     *              - promo_params 优惠参数 注：仅与支付宝协商后可用<br>
     *              - passback_params 公用回传参数，如果请求时传递了该参数，则返回给商户时会回传该参数。支付宝只会在同步返回（包括跳转回商户网站）和异步通知时将该参数原样返回。本参数必须进行UrlEncode之后才可以发送给支付宝。例如:merchantBizType%3d3C%26merchantBizNo%3d2016010101111<br>
     *              - business_params 	商户传入业务信息，具体值要和支付宝约定，应用于安全，营销等参数直传场景，格式为json格式 例如:{"data":"123"}<br>
     * @return
     */
    public Message<AlipayTradeAppPayResponse> tradeAppPay(AlipayTradeAppPayModel model) {
        AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
        request.setBizModel(model);
        request.setNotifyUrl(alipayConfig.getNotifyUrl());
        request.setNeedEncrypt(alipayConfig.needEncrypt());
        return tradeAppPay(request);
    }

    public Message<AlipayTradeAppPayResponse> tradeAppPay(AlipayTradeAppPayRequest request) {
        try {
            request.setNeedEncrypt(alipayConfig.needEncrypt());
            if (isDebug) {
                LOG.debug("[Pay Alipay] tradeAppPay:" + JsonTools.beanToJson(request));
            }
            AlipayTradeAppPayResponse execute = alipayClient.sdkExecute(request);
            LOG.info("[AlipayTradeAppPayResponse] " + JsonTools.beanToJson(execute));
            return response(execute);
        } catch (AlipayApiException e) {
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 手机网站支付接口2.0
     * <a href='https://docs.open.alipay.com/api_1/alipay.trade.wap.pay/'>文档</a>
     *
     * @param model <br>
     *              - subject [Y]	商品的标题/交易标题/订单标题/订单关键字等。<br>
     *              - out_trade_no [Y] 商户网站唯一订单号<br>
     *              - total_amount [Y] 订单总金额，单位为元，精确到小数点后两位，取值范围[0.01,100000000]<br>
     *              - timeout_express 该笔订单允许的最晚付款时间，逾期将关闭交易。取值范围：1m～15d。m-分钟，h-小时，d-天，1c-当天（1c-当天的情况下，无论交易何时创建，都在0点关闭）。 该参数数值不接受小数点， 如 1.5h，可转换为 90m。<br>
     *              - product_code 销售产品码，商家和支付宝签约的产品码<br>
     *              - body 对一笔交易的具体描述信息。如果是多种商品，请将商品描述字符串累加传给body。<br>
     *              - time_expire 绝对超时时间，格式为yyyy-MM-dd HH:mm。<br>
     *              - goods_type 商品主类型 :0-虚拟类商品,1-实物类商品<br>
     *              - promo_params 优惠参数 注：仅与支付宝协商后可用<br>
     *              - quit_url 用户付款中途退出返回商户网站的地址
     *              - passback_params 公用回传参数，如果请求时传递了该参数，则返回给商户时会回传该参数。支付宝只会在同步返回（包括跳转回商户网站）和异步通知时将该参数原样返回。本参数必须进行UrlEncode之后才可以发送给支付宝。例如:merchantBizType%3d3C%26merchantBizNo%3d2016010101111<br>
     *              - business_params 	商户传入业务信息，具体值要和支付宝约定，应用于安全，营销等参数直传场景，格式为json格式 例如:{"data":"123"}<br>
     * @return
     */
    public Message<AlipayTradeWapPayResponse> tradeWapPay(AlipayTradeWapPayModel model) {
        AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
        request.setBizModel(model);
        request.setNotifyUrl(alipayConfig.getNotifyUrl());
        try {
            if (isDebug) {
                LOG.debug("[Pay Alipay] tradeWapPay:" + JsonTools.beanToJson(model));
            }
            request.setNeedEncrypt(alipayConfig.needEncrypt());
            return response(alipayConfig.isUseCert() ? alipayClient.certificateExecute(request) : alipayClient.execute(request));
        } catch (AlipayApiException e) {
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 交易查询
     * <a href='https://docs.open.alipay.com/api_1/alipay.trade.query'>文档</a>
     *
     * @param model <br>
     *              - out_trade_no 支付时传入的商户订单号，与 trade_no 必填一个<br>
     *              - trade_no     支付时返回的支付宝交易号，与 out_trade_no 必填一个<br>
     * @return
     */
    public Message<AlipayTradeQueryResponse> tradeQuery(AlipayTradeQueryModel model) {
        if (isBlank(model.getOutTradeNo()) && isBlank(model.getTradeNo())) {
            return Message.fail("out_trade_no和trade_no必传一项");
        }
        try {
            if (isDebug) {
                LOG.debug("[Pay Alipay] tradeQuery:" + JsonTools.beanToJson(model));
            }
            AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();//创建API对应的request类
            request.setNeedEncrypt(alipayConfig.needEncrypt());
            request.setBizModel(model);
            return response(alipayConfig.isUseCert() ? alipayClient.certificateExecute(request) : alipayClient.execute(request));
        } catch (AlipayApiException e) {
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 关闭交易
     * <a href='https://opendocs.alipay.com/apis/api_1/alipay.trade.close'>文档</a>
     *
     * @param model <br>
     *              - trade_no 该交易在支付宝系统中的交易流水号。最短 16 位，最长 64 位。和out_trade_no不能同时为空，如果同时传了 out_trade_no和 trade_no，则以 trade_no为准。<br/>
     *              - out_trade_no 订单支付时传入的商户订单号,和支付宝交易号不能同时为空。 trade_no,out_trade_no如果同时存在优先取trade_no<br/>
     *              - operator_id     卖家端自定义的的操作员 ID<br>
     * @return
     */
    public Message<AlipayTradeCloseResponse> tradeClose(AlipayTradeCloseModel model) {
        if (isBlank(model.getOutTradeNo()) && isBlank(model.getTradeNo())) {
            return Message.fail("out_trade_no和trade_no必传一项");
        }
        try {
            if (isDebug) {
                LOG.debug("[Pay Alipay] tradeQuery:" + JsonTools.beanToJson(model));
            }
            AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();//创建API对应的request类
            request.setNeedEncrypt(alipayConfig.needEncrypt());
            request.setBizModel(model);
            return response(alipayConfig.isUseCert() ? alipayClient.certificateExecute(request) : alipayClient.execute(request));
        } catch (AlipayApiException e) {
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 退款
     * <a href='https://docs.open.alipay.com/api_1/alipay.trade.refund'>文档</a>
     *
     * @param model <br>
     *              - out_trade_no   支付时传入的商户订单号，与 trade_no 必填一个 <br>
     *              - trade_no       支付时返回的支付宝交易号，与 out_trade_no 必填一个 <br>
     *              - out_request_no 本次退款请求流水号，部分退款时必传 <br>
     *              - refund_amount  本次退款金额 <br>
     * @return
     */
    public Message<AlipayTradeRefundResponse> refund(AlipayTradeRefundModel model) {
        if (isBlank(model.getOutTradeNo()) && isBlank(model.getTradeNo())) {
            return Message.fail("out_trade_no和trade_no必传一项");
        }
        if (isBlank(model.getOutRequestNo()) && isBlank(model.getRefundAmount())) {
            return Message.fail("out_request_no和refund_amount不能为空");
        }
        try {
            if (isDebug) {
                LOG.debug("[Pay Alipay] refund params:" + JsonTools.beanToJson(model));
            }
            AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();//创建API对应的request类
            request.setNeedEncrypt(alipayConfig.needEncrypt());
            request.setBizModel(model);
            return response(alipayConfig.isUseCert() ? alipayClient.certificateExecute(request) : alipayClient.execute(request));
        } catch (AlipayApiException e) {
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 退款查询
     * <a href='https://docs.open.alipay.com/api_1/alipay.trade.fastpay.refund.query/'>文档</a>
     *
     * @param model<br> - trade_no 支付宝交易号，和商户订单号不能同时为空
     *                  - out_trade_no 订单支付时传入的商户订单号,和支付宝交易号不能同时为空。 trade_no,out_trade_no如果同时存在优先取trade_no
     *                  - out_request_no [Y] 请求退款接口时，传入的退款请求号，如果在退款请求时未传入，则该值为创建交易时的外部交易号
     *                  - org_pid 银行间联模式下有用，其它场景请不要使用；双联通过该参数指定需要查询的交易所属收单机构的pid;
     * @return
     */
    public Message<AlipayTradeFastpayRefundQueryResponse> refundQuery(AlipayTradeFastpayRefundQueryModel model) {
        if (isBlank(model.getOutTradeNo()) && isBlank(model.getTradeNo())) {
            return Message.fail("out_trade_no和trade_no必传一项");
        }
        if (StringTools.isBlank(model.getOutRequestNo())) {
            return Message.fail("out_request_no不能为空");
        }
        try {
            if (isDebug) {
                LOG.debug("[Pay Alipay] refundQuery params:" + JsonTools.beanToJson(model));
            }
            AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest();
            request.setNeedEncrypt(alipayConfig.needEncrypt());
            return response(alipayConfig.isUseCert() ? alipayClient.certificateExecute(request) : alipayClient.execute(request));
        } catch (AlipayApiException e) {
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 刷脸支付初始化
     *
     * @param metaInfo
     * @return
     * @see <a href="https://docs.open.alipay.com/api_46/zoloz.authentication.smilepay.initialize/">刷脸支付初始化</a>
     */
    public Message<ZolozAuthenticationSmilepayInitializeResponse> smilepayInit(String metaInfo) {
        try {
            if (isDebug) {
                LOG.debug("[Pay Alipay] zolozAuthenticationSmilepayInitialize params:" + metaInfo);
            }
            ZolozAuthenticationSmilepayInitializeRequest request = new ZolozAuthenticationSmilepayInitializeRequest();
            request.setBizContent(metaInfo);
            return response(alipayClient.sdkExecute(request));
        } catch (AlipayApiException e) {
            return Message.fail(e.getMessage());
        }
    }


    /**
     * 换取授权访问令牌
     *
     * @param code
     * @return
     * @throws AlipayApiException
     * @see <a href='https://docs.open.alipay.com/api_9/alipay.system.oauth.token'>文档</a>
     */
    public AlipaySystemOauthTokenResponse systemAuthToken(String code) throws AlipayApiException {
        AlipaySystemOauthTokenRequest request = new AlipaySystemOauthTokenRequest();
        request.setCode(code);
        request.setGrantType("authorization_code");
        return alipayConfig.isUseCert()
                ?
                alipayClient.certificateExecute(request)
                :
                alipayClient.execute(request)
                ;
    }

    /**
     * 刷新授权访问令牌
     *
     * @param refreshToken
     * @return
     * @throws AlipayApiException
     * @see <a href='https://docs.open.alipay.com/api_9/alipay.system.oauth.token'>文档</a>
     */
    public AlipaySystemOauthTokenResponse systemAuthTokenRefresh(String refreshToken) throws AlipayApiException {
        AlipaySystemOauthTokenRequest request = new AlipaySystemOauthTokenRequest();
        request.setRefreshToken(refreshToken);
        request.setGrantType("refreshToken");
        return alipayConfig.isUseCert() ? alipayClient.certificateExecute(request) : alipayClient.execute(request);
    }

    /**
     * 支付宝会员授权信息查询接口
     *
     * @param accessToken
     * @return
     * @see <a href='https://docs.open.alipay.com/api_2/alipay.user.info.share'>文档</a>
     */
    public AlipayUserInfoShareResponse userInfo(String accessToken) throws AlipayApiException {
        AlipayUserInfoShareRequest request = new AlipayUserInfoShareRequest();
        return alipayConfig.isUseCert()
                ?
                alipayClient.certificateExecute(request, accessToken)
                :
                alipayClient.execute(request, accessToken)
                ;
    }

    // ----预授权--- https://docs.open.alipay.com/20180417160701241302

    /**
     * 线上资金授权冻结
     * <p>
     * 创建支付宝授权订单并完成资金冻结。适用于线上场景完成资金授权, 例如从商户APP端拉起支付宝收银台完成冻结。
     * </p>
     *
     * @return
     * @see <a href='https://docs.open.alipay.com/20180417160701241302/vo4kv7/'>文档</a>
     * <pre>
     *     AlipayFundAuthOrderAppFreezeModel model = new AlipayFundAuthOrderAppFreezeModel();
     *     model.setOrderTitle("支付宝预授权");
     *     model.setOutOrderNo("2018077735255938023");//替换为实际订单号
     *     model.setOutRequestNo("2018077735255938232");//替换为实际请求单号，保证每次请求都是唯一的
     *     model.setPayeeUserId("payee_user_id");//payee_user_id,Payee_logon_id不能同时为空
     *     model.setPayeeLogonId("Payee_logon_id");
     *     model.setProductCode("PRE_AUTH_ONLINE");//PRE_AUTH_ONLINE为固定值，不要替换
     *     model.setAmount("0.02");
     *     //需要支持信用授权，该字段必传
     *     model.setExtraParam("{\"category\":\"xxx\",\"outStoreCode\":\"charge001\",\"outStoreAlias\":\"充电桩北京路点\"}"); //outStoreAlias将在用户端信用守护、支付信息、账单详情页展示
     *     //选填字段，指定支付渠道
     *     //model.setEnablePayChannels("[{\"payChannelType\":\"PCREDIT_PAY\"},{\"payChannelType\":\"MONEY_FUND\"},{\"payChannelType\":\"CREDITZHIMA\"}]");
     * </pre>
     */
    public Message<AlipayFundAuthOrderAppFreezeResponse> fundAuthOrderAppFreeze(AlipayFundAuthOrderAppFreezeModel model) {
        AlipayFundAuthOrderAppFreezeRequest request = new AlipayFundAuthOrderAppFreezeRequest();
        request.setBizModel(model);
        request.setNotifyUrl(alipayConfig.getNotifyUrl());
        try {
            AlipayFundAuthOrderAppFreezeResponse execute =
                    alipayConfig.isUseCert()
                            ?
                            alipayClient.sdkExecute(request)
                            :
                            alipayClient.execute(request)
                    ;
            return response(execute);
        } catch (AlipayApiException e) {
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 授权转支付
     * <p>
     * 商户因业务原因需要解冻用户授权资金并支付给卖家时，通过该接口创建交易订单并将授权资金解冻转支付给卖家，同时解冻剩余资金。
     * </p>
     *
     * @param model
     * @return
     * @see <a href='https://docs.open.alipay.com/20180417160701241302/qca164/'>文档</a>
     * <pre>
     * AlipayTradePayModel model = new AlipayTradePayModel();
     * model.setOutTradeNo("20180412100020088982"); // 预授权转支付商户订单号，为新的商户交易流水号；如果重试发起扣款，商户订单号不要变；
     * model.setProductCode("PRE_AUTH_ONLINE"); // 固定值PRE_AUTH_ONLINE
     * model.setAuthNo("2018041210002001660228733635"); // 填写预授权冻结交易号
     * model.setSubject("预授权转支付测试"); // 解冻转支付标题，用于展示在支付宝账单中
     * model.setTotalAmount("0.01"); // 结算支付金额
     * model.setSellerId(pid); // 填写卖家支付宝账户pid
     * model.setBuyerId(pay_user_id); // 填写预授权用户uid，通过预授权冻结接口返回的payer_user_id字段获取
     * model.setStoreId("test_store_id"); // 填写实际交易发生的终端编号，与预授权的outStoreCode保持一致即可
     * model.setBody("预授权解冻转支付测试"); // 可填写备注信息
     * model.setAuthConfirmMode("COMPLETE");//必须使用COMPLETE,传入该值用户剩余金额会自动解冻
     * </pre>
     */
    public Message<AlipayTradePayResponse> tradePay(AlipayTradePayModel model) {
        AlipayTradePayRequest request = new AlipayTradePayRequest();
        request.setBizModel(model);
        return tradePay(request);
    }

    /**
     * 资金授权解冻
     * <p>
     * 当资金授权发生之后一段时间内，由于买家或者商家等其他原因需要要解冻资金，商家可通过资金授权解冻接口将授权资金进行解冻，支付宝将在收到解冻请求并验证成功后，按解冻规则将冻结资金按原路进行解冻。
     * </p>
     * <b style="color:red">异步通知见文档</b>
     *
     * @param model
     * @return
     * @see <a href='https://docs.open.alipay.com/20180417160701241302/vy70oy/'>文档</a>
     * <pre>
     *    AlipayFundAuthOrderUnfreezeModel model = new AlipayFundAuthOrderUnfreezeModel();
     *    model.setAuthNo("2017120410002001390208978986"); // 支付宝资金授权订单号，在授权冻结成功时返回需要入库保存
     *    model.setOutRequestNo("UnfreezeRequestNo000003");//同一商户每次不同的资金操作请求，商户请求流水号不能重复,且与冻结流水号不同
     *    model.setAmount("0.01"); // 本次操作解冻的金额，单位为：元（人民币），精确到小数点后两位
     *    model.setRemark("预授权解冻"); // 商户对本次解冻操作的附言描述，长度不超过100个字母或50个汉字
     *    //选填字段，信用授权订单，针对信用全免订单，传入该值完结信用订单，形成芝麻履约记录
     *    model.setExtraParam("{\"unfreezeBizInfo\":\"{\\\"bizComplete\\\":\\\"true\\\"}\"}");
     * </pre>
     * <p>
     * <b style="color:red">注意事项:</b>
     * 1、支持全额解冻和部分解冻；<br/>
     * 2、解冻转支付时，若auth_confirm_mode=complete，无需调用解冻接口，支付宝端在扣款成功后会自动解冻剩余金额；若auth_confirm_mode=not_complete，在收到支付成功通知后，商户自行调用解冻接口将余额进行解冻；<br/>
     * 3、对于信用授权订单，扣款处理中或扣款失败（如：trade_ status=WAIT_BUYER_PAY或ORDER SUCCESS PAY INPROGRESS），若调用解冻接口，会中断支付宝扣款流程，请慎用；<br/>
     * 4、extraParam为选填字段，只对信用授权订单生效，若订单为信用全免订单，extraParam必须传入{"unfreezeBizInfo":"{\"bizComplete\":\"true\"}"}，将为用户生成芝麻履约订单。<br/>
     * 5、信用全免订单，比如押金200元，信用全免(免押金额200元)，全额解冻的时候需要传入200元，部分解冻的时候根据实际解冻金额传入；<br/>
     * </p>
     */
    public Message<AlipayFundAuthOrderUnfreezeResponse> fundAuthOrderUnFreeze(AlipayFundAuthOrderUnfreezeModel model) {
        AlipayFundAuthOrderUnfreezeRequest request = new AlipayFundAuthOrderUnfreezeRequest();
        request.setBizModel(model);
        request.setNotifyUrl(alipayConfig.getNotifyUrl());
        try {
            AlipayFundAuthOrderUnfreezeResponse execute =
                    alipayConfig.isUseCert()
                            ?
                            alipayClient.certificateExecute(request)
                            :
                            alipayClient.execute(request)
                    ;
            return response(execute);
        } catch (AlipayApiException e) {
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 资金授权撤销
     * <p>
     * 只有商户由于业务系统处理超时需要终止后续业务处理或者授权结果未知时可调用撤销，其他正常授权冻结的操作如需实现相同功能请调用资金授权解冻服务。提交资金授权后调用【资金授权操作查询】，没有明确的授权结果再调用【资金授权撤销】。
     * </p>
     *
     * @param model
     * @return
     * @see <a href='https://docs.open.alipay.com/20180417160701241302/axpg8p/'>文档</a>
     * <pre>
     *     AlipayFundAuthOperationCancelModel model = new AlipayFundAuthOperationCancelModel();
     *     //model.setAuthNo("2017120110002001390206804295"); // 支付宝资金授权订单号，在授权冻结成功时返回参数中获得
     *     model.setOutOrderNo("orderFreeze000005"); //商户的授权资金订单号，与支付宝的授权资金订单号不能同时为空
     *     //model.setOperationId("20171201317348823902"); //支付宝的授权资金操作流水号
     *     model.setOutRequestNo("requestNo000005");//与支付宝的授权资金操作流水号不能同时为空，与冻结流水号相同
     *     model.setRemark("预授权撤销"); // 商户对本次撤销操作的附言描述，长度不超过100个字母或50个汉字
     * </pre>
     * <b style="color:red">异步通知见文档</b>
     */
    public Message<AlipayFundAuthOperationCancelResponse> fundAuthCancel(AlipayFundAuthOperationCancelModel model) {
        AlipayFundAuthOperationCancelRequest request = new AlipayFundAuthOperationCancelRequest();
        request.setBizModel(model);
        request.setNotifyUrl(alipayConfig.getNotifyUrl());
        try {
            AlipayFundAuthOperationCancelResponse execute =
                    alipayConfig.isUseCert()
                            ?
                            alipayClient.certificateExecute(request)
                            :
                            alipayClient.execute(request)
                    ;
            return response(execute);
        } catch (AlipayApiException e) {
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 资金授权查询
     * <p>
     * 通过该接口可以查询单笔明细的详细信息，细分到每一次操作，如冻结、解冻。
     * </p>
     *
     * @param model
     * @return
     * @see <a href='https://docs.open.alipay.com/20180417160701241302/dfqldl/'>文档</a>
     * <pre>
     *    AlipayFundAuthOperationDetailQueryModel model = new AlipayFundAuthOperationDetailQueryModel();
     *     //model.setAuthNo("2017120110002001390206804295"); // 支付宝资金授权订单号，在授权冻结成功时返回参数中获得
     *     model.setOutOrderNo("orderFreeze000003"); //商户的授权资金订单号，与支付宝的授权资金订单号不能同时为空
     *     //model.setOperationId("20171201317348823902"); //支付宝的授权资金操作流水号，冻结成功同步返回
     *     model.setOutRequestNo("requestNo000003");//商户的授权资金操作流水号，与支付宝的授权资金操作流水号不能同时为空，该值为冻结或解冻是的outRequestNo
     * </pre>
     */
    public Message<AlipayFundAuthOperationDetailQueryResponse> fundAuthQuery(AlipayFundAuthOperationDetailQueryModel model) {
        AlipayFundAuthOperationDetailQueryRequest request = new AlipayFundAuthOperationDetailQueryRequest();
        request.setBizModel(model);
        try {
            AlipayFundAuthOperationDetailQueryResponse execute =
                    alipayConfig.isUseCert()
                            ?
                            alipayClient.certificateExecute(request)
                            :
                            alipayClient.execute(request)
                    ;
            return response(execute);
        } catch (AlipayApiException e) {
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 订单信息同步
     * <p>
     * 该接口用于商户向支付宝同步该笔订单相关业务信息
     * </p>
     *
     * @param model
     * @return
     * @see <a href='https://docs.open.alipay.com/20180417160701241302/pr7nfm/'>文档</a>
     * <pre>
     *    AlipayTradeOrderinfoSyncModel model = new AlipayTradeOrderinfoSyncModel();
     *     model.setBizType("CREDIT_AUTH");
     *     model.setTradeNo("2018061021001004680073956707");
     *     model.setOutRequestNo("HZ01RF001");
     *     model.setOrderBizInfo("{\"status\":\"COMPLETE\"}");
     * </pre>
     * <b>使用场景：</b>
     * 1、预授权订单为全信用或者部分信用授权订单；<br/>
     * 2、针对授权转支付扣款失败的订单，请在首次授权转支付失败T+N之后调用该接口（如果是同步用户违约状态，则转失败15天后同步）；<br/>
     * 3、orderInfo中的status包含：COMPLETE(用户已履约)、VIOLATED(用户已违约)；<br/>
     * 4、COMPLETE(用户已履约)：如果用户通过其他方式完成订单支付，请反馈该状态，芝麻将对用户形成一条良好履约记录；<br/>
     * 5、VIOLATED(用户已违约)：如果用户在约定时间（具体根据行业约定）内未完成订单支付，反馈该状态，芝麻将对用户记录一条负面记录；<br/>
     *
     *
     * <b>入参说明：</b>
     * 1、bizType 为 CREDIT_AUTH 为固定值；<br/>
     * 2、tradeNo为授权转支付返回的 tradeNo；<br/>
     *
     * <b>出参处理：</b>
     * <p>
     * 1、当返回ACQ.NOTIFY_STATUS_INVALID，表示当前无法反馈该状态，请稍后再试。
     */
    public Message<AlipayTradeOrderinfoSyncResponse> tradeInfoSync(AlipayFundAuthOperationCancelModel model) {
        AlipayTradeOrderinfoSyncRequest request = new AlipayTradeOrderinfoSyncRequest();
        request.setBizModel(model);
        try {
            AlipayTradeOrderinfoSyncResponse execute =
                    alipayConfig.isUseCert()
                            ?
                            alipayClient.certificateExecute(request)
                            :
                            alipayClient.execute(request)
                    ;
            return response(execute);
        } catch (AlipayApiException e) {
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 生活号网关
     *
     * @return
     * @see <a href='https://opendocs.alipay.com/open/197/105240'>文档</a>
     */
    public ModelAndView gateway(String service, String sign, String signType, String charset, String bizContent, HttpServletResponse response) {
        if (!verifySign(sign, signType, charset, bizContent)) {
            ServletTools.printObjJson(response, Message.fail("签名错误"));
            return null;
        }
        //5. 响应结果加签及返回
        try {

            //对响应内容加签
            String responseMsg = "";
            responseMsg = encryptAndSign(responseMsg,
                    alipayConfig.getAlipayPublicKey(),
                    alipayConfig.getPrivateKey(), charset,
                    false, true, signType);

            //http 内容应答
            ServletTools.printString(response, responseMsg);
        } catch (AlipayApiException alipayApiException) {
            //开发者可以根据异常自行进行处理
            alipayApiException.printStackTrace();
        }
        return null;
    }

    public String encryptAndSign(String bizContent, String alipayPublicKey, String cusPrivateKey, String charset,
                                 boolean isEncrypt, boolean isSign, String signType) throws AlipayApiException {
        StringBuilder sb = new StringBuilder();
        if (StringTools.isBlank(charset)) {
            charset = AlipayConstants.CHARSET_GBK;
        }
        sb.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>");
        if (isEncrypt) {// 加密
            sb.append("<alipay>");
            String encrypted = AlipaySignature.rsaEncrypt(bizContent, alipayPublicKey, charset);
            sb.append("<response>" + encrypted + "</response>");
            sb.append("<encryption_type>AES</encryption_type>");
            if (isSign) {
                String sign = AlipaySignature.rsaSign(encrypted, cusPrivateKey, charset, signType);
                sb.append("<sign>" + sign + "</sign>");
                sb.append("<sign_type>");
                sb.append(signType);
                sb.append("</sign_type>");
            }
            sb.append("</alipay>");
        } else if (isSign) {// 不加密，但需要签名
            sb.append("<alipay>");
            sb.append("<response>" + bizContent + "</response>");
            String sign = AlipaySignature.rsaSign(bizContent, cusPrivateKey, charset, signType);
            sb.append("<sign>" + sign + "</sign>");
            sb.append("<sign_type>");
            sb.append(signType);
            sb.append("</sign_type>");
            sb.append("</alipay>");
        } else {// 不加密，不加签
            sb.append(bizContent);
        }
        return sb.toString();
    }

    /**
     * 验签
     *
     * @param signature  待验证签名
     * @param signType   签名类型
     * @param charset    编码格式
     * @param bizContent 验证内容
     * @return
     */
    private boolean verifySign(String signature, String signType, String charset, String bizContent) {
        return signChecker.check(bizContent, signature, signType, charset);
    }

    /**
     * 单发模板消息
     *
     * @param request
     * @return
     * @see <a href='https://opendocs.alipay.com/apis/api_6/alipay.open.public.message.single.send'>文档</a>
     */
    public Message<AlipayOpenPublicMessageSingleSendResponse> openPublicMessageSingleSend(AlipayOpenPublicMessageSingleSendRequest request) {
        try {
            AlipayOpenPublicMessageSingleSendResponse response =
                    alipayConfig.isUseCert()
                            ?
                            alipayClient.certificateExecute(request)
                            :
                            alipayClient.execute(request)
                    ;
            return response(response);
        } catch (AlipayApiException e) {
            LOG.error("openPublicMessageSingleSend exception:", e);
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 小程序单发模板消息
     *
     * @param request
     * @return
     * @see <a href='https://opendocs.alipay.com/apis/009zv5'>文档</a>
     */
    public Message<AlipayOpenAppMiniTemplatemessageSendResponse> miniTemplatemessage(AlipayOpenAppMiniTemplatemessageSendRequest request) {
        try {

            AlipayOpenAppMiniTemplatemessageSendResponse response =
                    alipayConfig.isUseCert()
                            ?
                            alipayClient.certificateExecute(request)
                            :
                            alipayClient.execute(request)
                    ;
            return response(response);
        } catch (AlipayApiException e) {
            LOG.error("miniTemplatemessage exception:", e);
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 集中处理返回
     *
     * @param response
     * @param <T>
     * @return
     */
    public <T extends AlipayResponse> Message<T> response(T response) {
        if (response.isSuccess()) {
            if (isDebug) {
                LOG.debug("[Pay Alipay] response:" + JsonTools.beanToJson(response));
            }
            return Message.result(response);
        } else {
            LOG.error("支付宝错误:{}", JsonTools.beanToJson(response));
            return Message.fail(
                    StringTools.format("支付宝错误:{0}",
                            StringTools.isNotBlank(
                                    response.getSubMsg())
                                    ?
                                    response.getSubMsg()
                                    :(
                                    response.getMsg()
                                    ))
                    , response);
        }
    }

    // 带参推广二维码接口
    public Message<AlipayOpenPublicQrcodeCreateResponse> qrCodeCreate(CodeInfo codeInfo, String sceneId, String codeType, String expireSecond, boolean showLogo) {
        AlipayOpenPublicQrcodeCreateRequest request = new AlipayOpenPublicQrcodeCreateRequest();
        AlipayOpenPublicQrcodeCreateModel model = new AlipayOpenPublicQrcodeCreateModel();
        Scene scene = new Scene();
        scene.setSceneId(sceneId);
        codeInfo.setScene(scene);
        model.setCodeInfo(codeInfo);
        model.setCodeType(codeType);
        model.setExpireSecond(expireSecond);
        model.setShowLogo(showLogo ? "Y" : "N");
        request.setBizModel(model);
        try {
            AlipayOpenPublicQrcodeCreateResponse response = alipayConfig.isUseCert() ? alipayClient.certificateExecute(request) : alipayClient.execute(request);
            return response(response);
        } catch (Exception e) {
            LOG.error("支付宝错误:" + e.getMessage(), e);
            return Message.fail(StringTools.format("支付宝错误:{0}", e.getMessage()));
        }
    }

    // 带参推广短链接接口
    public Message<AlipayOpenPublicShortlinkCreateResponse> shortLinkCreate(String sceneId, String remark) {
        AlipayOpenPublicShortlinkCreateRequest request = new AlipayOpenPublicShortlinkCreateRequest();
        AlipayOpenPublicShortlinkCreateModel model = new AlipayOpenPublicShortlinkCreateModel();
        model.setSceneId(sceneId);
        model.setRemark(remark);
        request.setBizModel(model);
        try {
            AlipayOpenPublicShortlinkCreateResponse response = alipayConfig.isUseCert() ? alipayClient.certificateExecute(request) : alipayClient.execute(request);
            return response(response);
        } catch (Exception e) {
            LOG.error("支付宝错误:" + e.getMessage(), e);
            return Message.fail(StringTools.format("支付宝错误:{0}", e.getMessage()));
        }
    }

    /**
     * 生活号异步单发消息 alipay.open.public.message.custom.send(异步单发消息)
     * https://opendocs.alipay.com/apis/api_6/alipay.open.public.message.custom.send
     */
    public Message<AlipayOpenPublicMessageCustomSendResponse> messageCustomSend(String json) {
        AlipayOpenPublicMessageCustomSendRequest request = new AlipayOpenPublicMessageCustomSendRequest();
        request.setBizContent(json);
        try {
            AlipayOpenPublicMessageCustomSendResponse response = alipayConfig.isUseCert() ? alipayClient.certificateExecute(request) : alipayClient.execute(request);
            return response(response);
        } catch (Exception e) {
            LOG.error("支付宝错误:" + e.getMessage(), e);
            return Message.fail(StringTools.format("支付宝错误:{0}", e.getMessage()));
        }
    }

    /**
     * 判断用户是否关注公众号
     */
    public Message<AlipayOpenPublicUserFollowQueryResponse> publicUserFollowQuery(String openId) {
        try {
            AlipayOpenPublicUserFollowQueryRequest request = new AlipayOpenPublicUserFollowQueryRequest();
            Map<String, String> params = new HashMap<>();
            params.put("user_id", openId);
            request.setBizContent(JsonTools.beanToJson(params));
            AlipayOpenPublicUserFollowQueryResponse response = alipayConfig.isUseCert() ? alipayClient.certificateExecute(request) : alipayClient.execute(request);
            return response(response);
        } catch (Exception e) {
            LOG.error("支付宝错误 publicUserFollowQuery fail :" + e.getMessage(), e);
            return Message.fail(StringTools.format("支付宝错误:{0}", e.getMessage()));
        }
    }

    /**
     * 单笔转账接口
     *
     * @return
     * @see <a href='https://opendocs.alipay.com/apis/api_28/alipay.fund.trans.uni.transfer'>文档</a>
     */
    public Message<AlipayFundTransUniTransferResponse> fundTransUniTransfer(AlipayFundTransUniTransferModel transUniTransferModel) {
        try {
            if(!alipayConfig.isUseCert()){
                throw ApiException.of("当前支付宝配置不支持非证书公钥配置");
            }
            AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest();
            request.setBizModel(transUniTransferModel);
            AlipayFundTransUniTransferResponse response = alipayClient.certificateExecute(request);
            return response(response);
        } catch (Exception e) {
            LOG.error("支付宝错误:" + e.getMessage(), e);
            return Message.fail(StringTools.format("支付宝错误:{0}", e.getMessage()));
        }
    }

    /**
     * 查询转账订单接口
     *
     * @return
     * @see <a href="https://opendocs.alipay.com/apis/api_28/alipay.fund.trans.order.query">文档</>
     */
    public Message<AlipayFundTransOrderQueryResponse> fundTransOrderQuery(AlipayFundTransOrderQueryModel alipayFundTransOrderQueryModel){
        AlipayFundTransOrderQueryRequest request = new AlipayFundTransOrderQueryRequest();
        request.setBizModel(alipayFundTransOrderQueryModel);
        try {
            if(!alipayConfig.isUseCert()){
                throw ApiException.of("当前支付宝配置不支持非证书公钥配置");
            }
            AlipayFundTransOrderQueryResponse response = alipayClient.certificateExecute(request);
            return response(response);
        } catch (AlipayApiException e) {
            LOG.error("支付宝错误:" + e.getMessage(), e);
            return Message.fail(StringTools.format("支付宝错误:{0}", e.getMessage()));
        }
    }

    public boolean signVerifyRsaV1(Map<String, String> params, String publicKey, String charset, String signType) throws AlipayApiException{
        if(alipayConfig.isUseCert()){
            return AlipaySignature.rsaCertCheckV1(params, publicKey, charset, signType);
        }else{
            return AlipaySignature.rsaCheckV1(params, publicKey, charset, signType);
        }
    }

    @Override
    public void afterPropertiesSet() {
        if(!alipayConfig.check()){
            LOG.info(JsonTools.beanToJson(alipayConfig));
            throw AjavaerException.of("支付宝配置不完整");
        }
        if (alipayConfig.needEncrypt()) {
            if(alipayConfig.isUseCert()){
                try {
                    alipayClient = new DefaultAlipayClient(getCertRequest(true));
                } catch (AlipayApiException e) {
                    LOG.error("构建证书方式的AlipayClient出现异常:code->{},msg->{}",e.getErrCode(),e.getErrMsg());
                    LOG.error("构建证书方式的AlipayClient出现异常",e);
                }
            }else{
                alipayClient = new DefaultAlipayClient(alipayConfig.getGatewayUrl(), alipayConfig.getAppid(), alipayConfig.getPrivateKey(), "json", alipayConfig.getCharset(), alipayConfig.getAlipayPublicKey(), alipayConfig.getSignType(), alipayConfig.getAesKey(), "AES");
            }
        } else {
            if(alipayConfig.isUseCert()){
                try {
                    alipayClient = new DefaultAlipayClient(getCertRequest(false));
                } catch (AlipayApiException e) {
                    LOG.error("构建证书方式的AlipayClient出现异常:code->{},msg->{}",e.getErrCode(),e.getErrMsg());
                    LOG.error("构建证书方式的AlipayClient出现异常",e);
                }
            }else{
                alipayClient = new DefaultAlipayClient(alipayConfig.getGatewayUrl(), alipayConfig.getAppid(), alipayConfig.getPrivateKey(), "json", alipayConfig.getCharset(), alipayConfig.getAlipayPublicKey(), alipayConfig.getSignType());
            }
        }
        this.signChecker = new DefaultSignChecker(alipayConfig.getAlipayPublicKey());
    }

    private CertAlipayRequest getCertRequest(boolean isEncrypt){
        Security.addProvider(new BouncyCastleProvider());
        try {
            // 设置 应用公钥证书SN
            alipayConfig.setAppCertSn(AlipaySignature.getCertSN(alipayConfig.getAppCertPublicKeyPath()));
        } catch (AlipayApiException e) {
            LOG.error("获取应用公钥证书SN出现错误:",e);
        }
        try {
            // 设置 支付宝根证书SN
            alipayConfig.setAlipayRootCertSn(AntCertificationUtil.getRootCertSN(readFileToString(alipayConfig.getAlipayRootCertPath())));
        } catch (AlipayApiException e) {
            LOG.error("获取支付宝根证书SN出现错误:",e);
        }
        CertAlipayRequest certAlipayRequest = new CertAlipayRequest();
        //设置网关地址
        certAlipayRequest.setServerUrl(alipayConfig.getGatewayUrl());
        //设置应用Id
        certAlipayRequest.setAppId(alipayConfig.getAppid());
        //设置应用私钥
        certAlipayRequest.setPrivateKey(alipayConfig.getPrivateKey());
        //设置请求格式，固定值json
        certAlipayRequest.setFormat("json");
        //设置字符集
        certAlipayRequest.setCharset(alipayConfig.getCharset());
        //设置签名类型
        certAlipayRequest.setSignType(alipayConfig.getSignType());
        //设置应用公钥证书路径
        certAlipayRequest.setCertPath(alipayConfig.getAppCertPublicKeyPath());
        //设置支付宝公钥证书路径
        certAlipayRequest.setAlipayPublicCertPath(alipayConfig.getAlipayCertPublicKeyPath());
        //设置支付宝根证书路径
        certAlipayRequest.setRootCertPath(alipayConfig.getAlipayRootCertPath());
        if(isEncrypt){
            //设置加密类型AES
            certAlipayRequest.setEncryptType("AES");
            //设置加密key
            certAlipayRequest.setEncryptor(alipayConfig.getAesKey());
        }
        return certAlipayRequest;
    }

    private String readFileToString(String rootCertPath) throws AlipayApiException {
        try {
            File file = new File(rootCertPath);
            return FileUtils.readFileToString(file, StandardCharsets.UTF_8);
        }catch (IOException e) {
            throw new AlipayApiException(e);
        }
    }

}
